﻿/* 
 * Copyright 2010 The Apache Software Foundation.
 *
 * Licensed under the Apache License, Version 2.0 (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Ngilead.Core;
using NHibernate;
using Ngilead.Core.Utils;
using log4net;
using NHibernate.Proxy;
using Ngilead.Core.Exception;
using NHibernate.Metadata;
using NHibernate.Impl;
using System.Reflection;
using NHibernate.Persister.Entity;
using NHibernate.Tuple.Entity;
using NHibernate.Tuple;
using NHibernate.Type;
using NHibernate.UserTypes;
using NHibernate.Collection;
using System.Collections;
using NHibernate.Mapping;
using NHibernate.Engine;
using NHibernate.Persister.Collection;
using NHibernate.Collection.Generic;
using Iesi.Collections;
using Iesi.Collections.Generic;
using Ngilead.Common;

namespace Ngilead.Nhibernate
{
    /// <summary>
    /// Persistent helper for Hibernate implementation
    /// Centralizes the SessionFactory and add some needed methods.
    /// Not really a singleton, since there can be as many HibernateUtil instance as different sessionFactories
    /// </summary>
    public class NhibernatePersistentUtil : IPersistenceUtil
    {
        /*----------------------------------------------------------------------------------------*/
        #region Serialized proxy informations map constants

        /// <summary>
        /// Proxy id
        /// </summary>
        private static readonly string ID = "id";

        /// <summary>
        /// Persistent collection class name
        /// </summary>
        private static readonly string CLASS_NAME = "class";

        /// <summary>
        /// Persistent collection role
        /// </summary>
        private static readonly string ROLE = "role";

        /// <summary>
        /// Persistent collection PK ids
        /// </summary>
        private static readonly string KEY = "key";

        /// <summary>
        /// Persistent collection ids list
        /// </summary>
        private static readonly string ID_LIST = "idList";

        /// <summary>
        /// Persistent collection element type
        /// </summary>
        private static readonly string ITEM_TYPE = "itemType";

        /// <summary>
        /// Persistent map values list
        /// </summary>
        private static readonly string VALUE_LIST = "valueList";

        /// <summary>
        /// Persistent map values type
        /// </summary>
        private static readonly string VALUE_TYPE = "valueType";


        #endregion
        /*----------------------------------------------------------------------------------------*/

        /*----------------------------------------------------------------------------------------*/
        #region Ctor and Singleton

        public NhibernatePersistentUtil(ISessionFactory sessionFactory)
        {
            SessionFactory = sessionFactory;
            _session = new ThreadLocal<NHibernateSession>();
            _persistenceMap = new Dictionary<Type, bool>();
            _unehancementMap = new Dictionary<Type, Type>();

            // Filling persistence map with primitive types
            _persistenceMap.Add(typeof(byte), false);
            _persistenceMap.Add(typeof(Int16), false);
            _persistenceMap.Add(typeof(Int32), false);
            _persistenceMap.Add(typeof(Int64), false);
            _persistenceMap.Add(typeof(float), false);
            _persistenceMap.Add(typeof(double), false);
            _persistenceMap.Add(typeof(string), false);
        }

        /// <summary>
        /// Empty constructor
        /// </summary>
        public NhibernatePersistentUtil()
            : this(null)
        {
        }

        private static NhibernatePersistentUtil _instance;

        /// <summary>
        /// The pseudo unique instance of the singleton
        /// </summary>
        /// <returns>the unique instance of the singleton</returns>
        public static NhibernatePersistentUtil Instance
        {
            get
            {
                if (_instance == null)
                {
                    _instance = new NhibernatePersistentUtil();
                }
                return _instance;
            }
        }

        /// <summary>
        /// Get the pseudo unique instance of the singleton
        /// </summary>
        /// <returns>the unique instance of the singleton</returns>
        public static NhibernatePersistentUtil GetInstance()
        {
            return Instance;
        }

        #endregion
        /*----------------------------------------------------------------------------------------*/

        /*----------------------------------------------------------------------------------------*/
        #region Private variables

        /// <summary>
        /// Log channel
        /// </summary>
        private static ILog logger = LogManager.GetLogger(typeof(NhibernatePersistentUtil));

        /// <summary>
        /// The current opened session
        /// </summary>
        private ThreadLocal<NHibernateSession> _session;

        /// <summary>
        /// The associated session factory
        /// </summary>
        private ISessionFactory _sessionFactory;

        /// <summary>
        /// The persistance map, with persistance status of all classes
        /// including persistent component classes (performance improvement)
        /// </summary>
        private Dictionary<Type, bool> _persistenceMap;


        /// <summary>
        /// The unenhancement map, used for performance purpose
        /// </summary>
        private Dictionary<Type, Type> _unehancementMap;


        #endregion
        /*----------------------------------------------------------------------------------------*/

        /*----------------------------------------------------------------------------------------*/
        #region Public properties

        /// <summary>
        /// The Hibernate session factory
        /// </summary>
        public ISessionFactory SessionFactory
        {
            get
            {
                return _sessionFactory;
            }
            set
            {
                _sessionFactory = value;
            }
        }

        #endregion
        /*----------------------------------------------------------------------------------------*/

        /*----------------------------------------------------------------------------------------*/
        #region IPersistenceUtil Membres

        public void OpenSession()
        {
            //TU OK
            //	Precondition checking
            //
            if (SessionFactory == null)
            {
                throw new ArgumentNullException("No Hibernate Session Factory defined !");
            }

            //	Open a the existing session
            //
            ISession session = null;
            bool created = false;
            try
            {
                session = SessionFactory.GetCurrentSession();
                if (session.IsConnected == false)
                {
                    session = SessionFactory.OpenSession();
                    created = true;
                }
            }
            catch (HibernateException ex)
            {
                if (logger.IsDebugEnabled)
                {
                    logger.Debug("No current session, opening a new one", ex);
                }
                session = SessionFactory.OpenSession();
                created = true;
            }

            //	Store the session in ThreadLocal
            //
            _session.Set(new NHibernateSession(session, created));
        }

        public void CloseCurrentSession()
        {
            //TU OK
            NHibernateSession hSession = _session.Get();
            if (hSession != null)
            {
                // Only close session that we created
                if (hSession.Created == true)
                {
                    hSession.Session.Close();
                }
                _session.Set(null);
            }
        }

        public object Load(object id, Type persistentClass)
        {
            //	Unenhance persistent class if needed
            persistentClass = GetUnenhancedClass(persistentClass);

            //	Load the entity
            return GetSession().Session.Get(persistentClass, id);
        }

        public void FlushIfNeeded()
        {
            ISession session = GetCurrentSession();
            if (session != null)
            {
                if (logger.IsDebugEnabled)
                {
                    logger.Debug("Flushing session !");
                }
                session.Flush();
            }
        }

        public object GetId(object pojo)
        {
            //TU OK
            return GetId(pojo, GetPersistentClass(pojo));
        }

        public object GetId(object pojo, Type persistentClass)
        {
            //TU OK
            //	Precondition checking
            if (SessionFactory == null)
            {
                throw new ArgumentException("No NHibernate Session Factory defined !");
            }

            //	Persistence checking
            if (IsPersistentClass(persistentClass) == false)
            {
                //	Not an hibernate Class !
                if (logger.IsDebugEnabled)
                {
                    logger.Debug(String.Format("{0} is not persistent", persistentClass));
                    DumpPersistenceMap();
                }
                throw new NotPersistentObjectException(pojo);
            }

            //	Retrieve Class<?> hibernate metadata
            IClassMetadata hibernateMetadata = SessionFactory.GetClassMetadata(GetEntityName(persistentClass, pojo));
            if (hibernateMetadata == null)
            {
                //	Component class (persistent but not metadata) : no associated id
                //	So must be considered as transient
                throw new Ngilead.Core.Exception.ComponentTypeException(pojo);
            }

            //	Retrieve ID
            object id = null;
            Type pojoClass = GetPersistentClass(pojo);
            if (persistentClass.Equals(pojoClass))
            {
                //	Same class for pojo and hibernate class
                if (pojo is INHibernateProxy)
                {
                    //	To prevent LazyInitialisationException
                    id = ((INHibernateProxy)pojo).HibernateLazyInitializer.Identifier;
                }
                else
                {
                    //	Otherwise : use metada
                    id = hibernateMetadata.GetIdentifier(pojo, EntityMode.Poco);
                }
            }
            else
            {
                //	DTO case : invoke the method with the same name
                String propertyName = hibernateMetadata.IdentifierPropertyName;

                try
                {
                    // compute property name
                    propertyName = propertyName.Substring(0, 1).ToUpper() +
                               propertyName.Substring(1);

                    // Find getter method
                    PropertyInfo property = pojoClass.GetProperties().Where(c => c.Name == propertyName).SingleOrDefault();
                    if (property == null)
                    {
                        throw new System.Exception("Cannot find property " + propertyName + " for Type " + pojoClass);
                    }
                    id = property.GetValue(pojo, (Object[])null);
                }
                catch (Exception ex)
                {
                    throw new System.Exception("Invocation exception ", ex);
                }
            }

            //	Post condition checking
            //
            if (IsUnsavedValue(pojo, id, persistentClass))
            {
                throw new Ngilead.Core.Exception.TransientObjectException(pojo);
            }
            return id;
        }

        public bool IsPersistentPojo(object pojo)
        {
            //TU OK
            //	Precondition checking
            if (pojo == null)
            {
                return false;
            }

            //	Try to get the ID : if an exception is thrown
            //	the pojo is not persistent...
            try
            {
                GetId(pojo);
                return true;
            }
            catch (Ngilead.Core.Exception.TransientObjectException)
            {
                return false;
            }
            catch (NotPersistentObjectException)
            {
                return false;
            }
        }

        public bool IsPersistentClass(Type clazz)
        {
            //TU OK
            //	Precondition checking
            if (SessionFactory == null)
            {
                throw new ArgumentNullException("No Hibernate Session Factory defined !");
            }

            //	Check proxy (based on beanlib Unenhancer class)
            clazz = GetUnenhancedClass(clazz);

            //	Look into the persistence map
            lock (_persistenceMap)
            {
                if (clazz != null && _persistenceMap.Keys.Contains(clazz))
                {
                    return _persistenceMap[clazz];
                }
            }

            //	First clall for this Class<?> : compute persistence class
            ComputePersistenceForClass(clazz);
            return _persistenceMap[clazz];
        }

        public Type GetUnenhancedClass(Type clazz)
        {
            //TU OK
            //	Map checking
            if (clazz != null && !_unehancementMap.Keys.Contains(clazz))
            {
                //	Based on beanlib unEnhancer class
                Type unenhancedClass = UnenhanceClass(clazz);
                _unehancementMap.Add(clazz, unenhancedClass);
            }
            return _unehancementMap[clazz];
        }

        public bool IsEnhanced(Type clazz)
        {
            //TU OK
            //	Compare class to unenhanced class
            return (clazz != GetUnenhancedClass(clazz));
        }

        public bool IsPersistentCollection(Type collectionClass)
        {
            return (typeof(AbstractPersistentCollection)).IsAssignableFrom(collectionClass);
        }

        public bool IsDenotingPersistentCollection(Dictionary<string, object> proxyInformation)
        {
            //  Precondition checking
            //
            if ((proxyInformation == null) ||
                (proxyInformation.ContainsKey(CLASS_NAME) == false))
            {
                return false;
            }

            //  Get class name
            //
            string className = proxyInformation[CLASS_NAME] as string;

            return ((typeof(PersistentBag).Name == className) ||
                    (typeof(PersistentGenericBag<>).Name == className) ||
                    (typeof(PersistentList).Name == className) ||
                    (typeof(PersistentGenericList<>).Name == className) ||
                    (typeof(PersistentSet).Name == className) ||
                    (typeof(PersistentGenericSet<>).Name == className));
        }

        public Type GetUnderlyingCollectionType(Type collectionClass)
        {
            if ((typeof(PersistentSet)).IsAssignableFrom(collectionClass))
            {
                //  Persistent Set
                //
                return typeof(HashedSet<>);
            }
            else if (((typeof(PersistentList)).IsAssignableFrom(collectionClass) ||
                     ((typeof(PersistentBag)).IsAssignableFrom(collectionClass))))
            {
                //  Persistent list or bag
                //
                return typeof(List<>);
            }
            else
            {
                //  Unsupported Persistent collection
                //
                throw new NotImplementedException("Unsupported persistent collection : " + collectionClass.GetType());
            }
        }

        public bool IsPersistentDictionary(Type collectionClass)
        {
            return (typeof(NHibernate.Collection.PersistentMap)).IsAssignableFrom(collectionClass);
        }

        public bool IsDenotingPersistentDictionary(Dictionary<string, object> proxyInformation)
        {
            //  Precondition checking
            //
            if ((proxyInformation == null) ||
                (proxyInformation.ContainsKey(CLASS_NAME) == false))
            {
                return false;
            }

            //  Get class name
            //
            string className = proxyInformation[CLASS_NAME] as string;

            return ((typeof(PersistentMap).Name == className) ||
                    (typeof(PersistentGenericMap<,>).Name == className));
        }

        public bool IsInitialized(object proxy)
        {
            //TU OK
            return NHibernateUtil.IsInitialized(proxy);
        }

        public bool IsPropertyInitialized(object proxy, string propertyName)
        {
            return NHibernateUtil.IsPropertyInitialized(proxy, propertyName);
        }

        public void Initialize(object proxy)
        {
            NHibernateUtil.Initialize(proxy);
        }

        public Dictionary<string, object> SerializeEntityProxy(object proxy)
        {
            //TU OK
            //	Precondition checking
            if (proxy == null)
            {
                return null;
            }

            //	Serialize needed proxy informations
            Dictionary<string, object> result = new Dictionary<string, object>();
            result.Add(CLASS_NAME, GetUnenhancedClass(proxy.GetType()).FullName);
            result.Add(ID, GetId(proxy));

            return result;
        }

        public object CreateEntityProxy(Dictionary<string, object> proxyInformations)
        {
            //TU OK
            //	Get needed proxy inforamtions
            object id = proxyInformations[ID];
            String entityName = (String)proxyInformations[CLASS_NAME];

            //	Create the associated proxy
            return GetSession().Session.Load(entityName, id);
        }

        public Dictionary<string, object> SerializePersistentDictionary(object persistentDictionnary)
        {
            //	Create serialization map
            Dictionary<string, object> result = new Dictionary<string, object>();

            //	Get parameters
            PersistentMap temp = persistentDictionnary as PersistentMap;
            if (temp == null)
            {
                throw new System.Exception("Unexpected Persistent Dictionnary type : " + persistentDictionnary.GetType());
            }

            Type collectionType = temp.GetType();
            result.Add(CLASS_NAME, collectionType.Name);
            result.Add(ROLE, temp.Role);
            result.Add(KEY, temp.Key);

            result.Add(ITEM_TYPE, collectionType.GetGenericArguments()[0].FullName);
            result.Add(VALUE_TYPE, collectionType.GetGenericArguments()[1].FullName);

            if (IsInitialized(temp) == true)
            {
                //  Store keys
                List<SerializableId> keyList = CreateIdList(temp.Keys);
                if (keyList != null)
                {
                    result.Add(ID_LIST, keyList);

                    //	Store values (only if keys are persistents)
                    List<SerializableId> valueList = CreateIdList(temp.Values);
                    if (valueList != null)
                    {
                        result.Add(VALUE_LIST, valueList);
                    }
                }
            }
            return result;
        }

        public Dictionary<string, object> SerializePersistentCollection(object persistentCollection)
        {
            //	Create serialization map
            Dictionary<string, object> result = new Dictionary<string, object>();

            //	Get parameters
            AbstractPersistentCollection collection = (AbstractPersistentCollection)persistentCollection;
            Type collectionType = collection.GetType();
            result.Add(CLASS_NAME, collectionType.Name);
            result.Add(ROLE, collection.Role);
            result.Add(KEY, collection.Key);

            result.Add(ITEM_TYPE, collectionType.GetGenericArguments()[0].FullName);

            if (IsInitialized(collection))
            {
                //	Store snapshot ids
                if (collectionType.IsGenericType &&
                    (collectionType.BaseType == typeof(PersistentBag)
                    || collectionType.BaseType == typeof(PersistentList)
                    || collectionType.BaseType == typeof(PersistentMap)
                    || collectionType.BaseType == typeof(PersistentSet))
                    )
                {
                    ICollection<object> snapshot = (ICollection<object>)collection.StoredSnapshot;
                    List<SerializableId> listCreated = CreateIdList((ICollection)snapshot);
                    if (listCreated != null)
                    {
                        result.Add(ID_LIST, listCreated);
                    }
                }
                else
                {
                    throw new System.Exception("Unexpected Persistent collection type : " + collection.GetType());
                }
            }

            return result;
        }

        public object CreatePersistentDictionary(object parent, Dictionary<string, object> proxyInformations, object underlyingDictionary)
        {
            //	Create original map
            //Finding the generic type : 
            Type[] twoGenerics = new Type[]{
                GetType(proxyInformations[ITEM_TYPE] as string), 
                GetType(proxyInformations[VALUE_TYPE] as string)};

            MethodInfo tempMI = typeof(NhibernatePersistentUtil)
                .GetMethod("CreateOriginalDictionary")
                .MakeGenericMethod(twoGenerics);
            object originalDictionary = tempMI.Invoke(this, new object[] { proxyInformations, underlyingDictionary });

            bool hasDictionaryChanged = AreDifferent((IDictionary)originalDictionary, (IDictionary)underlyingDictionary);
            if ((hasDictionaryChanged == true) &&
                (originalDictionary == null))
            {
                //  The dictionary was empty, but was filled on client side
                //  We have to have an empty 'originalDictionary' instead of a null one
                //  since NHibernate consider null collections has not initialized (?!),
                //  leading to a LazyInitializationException when we will try to add the new
                //  items at the end of this method
                //
                ConstructorInfo constructor = typeof(Dictionary<,>).MakeGenericType(twoGenerics).GetConstructor(System.Type.EmptyTypes);
                originalDictionary = constructor.Invoke((object[])null);
            }

            //	Create collection for the class name
            String className = (String)proxyInformations[CLASS_NAME];

            NHibernateSession session = GetSession();
            AbstractPersistentCollection collection = null;

            if (className.StartsWith(typeof(PersistentGenericMap<,>).Name))
            {
                Type temp = typeof(PersistentGenericMap<,>);
                //	Persistent map creation
                Type toAdd = temp.MakeGenericType(twoGenerics);
                if (originalDictionary == null)
                {
                    collection = (AbstractPersistentCollection)Activator.CreateInstance(toAdd, (ISessionImplementor)session.Session);
                }
                else
                {
                    collection = (AbstractPersistentCollection)Activator.CreateInstance(toAdd, (ISessionImplementor)session.Session, (IDictionary)originalDictionary);
                }
            }
            else
            {
                throw new Exception("Unknown persistent map class name : " + className);
            }

            //	Fill with serialized parameters
            //
            String role = (String)proxyInformations[ROLE];
            object snapshot = null;
            if (originalDictionary != null)
            {
                //	Create snapshot
                //
                ICollectionPersister collectionPersister = ((ISessionFactoryImplementor)_sessionFactory).GetCollectionPersister(role);
                snapshot = collection.GetSnapshot(collectionPersister);
            }

            collection.SetSnapshot(proxyInformations[KEY],
                                   role, snapshot);

            //	Owner
            //
            collection.Owner = parent;

            //	Update persistent collection
            //
            if (hasDictionaryChanged)
            {
                if (originalDictionary != null)
                {
                    ((IDictionary)collection).Clear();
                }


                if (underlyingDictionary != null)
                {
                    IDictionary dic = (IDictionary)underlyingDictionary;
                    foreach (Object key in dic.Keys)
                    {
                        ((IDictionary)collection).Add(key, dic[key]);
                    }
                }

                collection.Dirty();
            }

            return (object)collection;
        }

        public object CreatePersistentCollection(object parent, Dictionary<string, object> proxyInformations, object underlyingCollection)
        {
            //Finding the generic type : 
            Type itemType = GetType(proxyInformations[ITEM_TYPE] as string);


            MethodInfo tempMI = typeof(NhibernatePersistentUtil)
                .GetMethod("CreateOriginalCollection")
                .MakeGenericMethod(itemType);
            object originalCollection = tempMI.Invoke(this, new object[] { proxyInformations, underlyingCollection });

            bool hasCollectionChanged = AreDifferent((ICollection)originalCollection, (ICollection)underlyingCollection);
            if ((hasCollectionChanged == true) &&
                (originalCollection == null))
            {
                //  The collection was empty, but was filled on client side
                //  We have to have an empty 'originalCollection' instead of a null one
                //  since NHibernate consider null collections has not initialized (?!),
                //  leading to a LazyInitializationException when we will try to add the new
                //  items at the end of this method
                //
                ConstructorInfo constructor = typeof(List<>).MakeGenericType(itemType).GetConstructor(System.Type.EmptyTypes);
                originalCollection = constructor.Invoke((object[])null);
            }


            //	Create Persistent collection for the class name
            string className = (string)proxyInformations[CLASS_NAME];

            NHibernateSession session = GetSession();
            AbstractPersistentCollection collection = null;

            Type temp = null;
            Boolean isISet = false;
            if (className.StartsWith(typeof(PersistentGenericBag<>).Name))
            {
                temp = typeof(PersistentGenericBag<>);
            }
            else if (className.StartsWith(typeof(PersistentGenericList<>).Name))
            {
                temp = typeof(PersistentGenericList<>);
            }
            else if (className.StartsWith(typeof(PersistentGenericSet<>).Name))
            {
                temp = typeof(PersistentGenericSet<>);
                isISet = true;
            }
            else if (className.StartsWith(typeof(PersistentIdentifierBag<>).Name))
            {
                temp = typeof(PersistentIdentifierBag<>);
            }
            else
            {
                throw new Exception("Unknown persistent collection class name : " + className);
            }

            Type toAdd = temp.MakeGenericType(itemType);
            if (originalCollection == null)
            {
                collection = (AbstractPersistentCollection)Activator.CreateInstance(toAdd, (ISessionImplementor)session.Session);
            }
            else
                if (isISet)
                {
                    Type set = typeof(HashedSet<>);
                    Type genericSet = set.MakeGenericType(itemType);
                    //HashedSet s = new HashedSet((ICollection)originalCollection);
                    collection = (AbstractPersistentCollection)Activator.CreateInstance(toAdd, (ISessionImplementor)session.Session, Activator.CreateInstance(genericSet, (ICollection)originalCollection));
                }
                else
                {
                    collection = (AbstractPersistentCollection)Activator.CreateInstance(toAdd, (ISessionImplementor)session.Session, (ICollection)originalCollection);
                }

            //	Fill with serialized parameters
            String role = (String)proxyInformations[ROLE];
            object snapshot = null;
            if (originalCollection != null)
            {
                //	Create snapshot
                ICollectionPersister collectionPersister = ((ISessionFactoryImplementor)_sessionFactory).GetCollectionPersister(role);
                snapshot = collection.GetSnapshot(collectionPersister);
            }

            collection.SetSnapshot(proxyInformations[KEY],
                                   role, snapshot);

            //	Owner
            collection.Owner = parent;

            //	Update persistent collection
            if (hasCollectionChanged)
            {
                if (isISet)
                {
                    if (originalCollection != null)
                    {
                        ((ISet)collection).Clear();
                    }

                    if (underlyingCollection != null)
                    {
                        foreach (var item in (ISet)underlyingCollection)
                        {
                            ((ISet)collection).Add(item);
                        }
                    }
                }
                else
                {
                    if (originalCollection != null)
                    {
                        ((IList)collection).Clear();
                    }

                    if (underlyingCollection != null)
                    {
                        foreach (var item in (IList)underlyingCollection)
                        {
                            ((IList)collection).Add(item);
                        }
                    }
                }
                collection.Dirty();
            }
            return collection;
        }

        public object LoadAssociation(Type parentClass, object parentId, string propertyName)
        {
            //TU OK
            //	Create query
            StringBuilder queryString = new StringBuilder();
            queryString.Append("SELECT item FROM ");
            queryString.Append(parentClass.Name);
            queryString.Append(" item JOIN FETCH item.");
            queryString.Append(propertyName);
            queryString.Append(" WHERE item.id = :id");
            if (logger.IsDebugEnabled)
            {
                logger.Debug(String.Format("Query is '{0}'", queryString.ToString()));
            }

            //	Fill query
            NHibernateSession session = GetSession();
            IQuery query = session.Session.CreateQuery(queryString.ToString());
            query.SetParameter("id", parentId);

            //	Execute query
            return query.UniqueResult();
        }

        public List<object> ExecuteQuery(string query, List<object> parameters)
        {
            if (logger.IsDebugEnabled)
            {
                logger.Debug(String.Format("Executing query '{0}'", query.ToString()));
            }

            //	Fill query
            //
            NHibernateSession session = GetSession();
            IQuery hqlQuery = session.Session.CreateQuery(query);

            //	Fill parameters
            //
            if (parameters != null)
            {
                for (int index = 0; index < parameters.Count; index++)
                {
                    hqlQuery.SetParameter(index, parameters[index]);
                }
            }

            //	Execute query
            //
            return (List<Object>)hqlQuery.List();
        }

        public List<object> ExecuteQuery(string query, Dictionary<string, object> parameters)
        {
            if (logger.IsDebugEnabled)
            {
                logger.Debug(String.Format("Executing query '{0}'", query.ToString()));
            }

            //	Fill query
            //
            NHibernateSession session = GetSession();
            IQuery hqlQuery = session.Session.CreateQuery(query);

            //	Fill parameters
            //
            if (parameters != null)
            {
                foreach (var item in parameters)
                {
                    hqlQuery.SetParameter(item.Key, item.Value);
                }
            }

            //	Execute query
            //
            return (List<Object>)hqlQuery.List();
        }

        #endregion
        /*----------------------------------------------------------------------------------------*/

        /*----------------------------------------------------------------------------------------*/
        #region Private functions

        /// <summary>
        /// Create a collection map
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="collection"></param>
        /// <returns></returns>
        private Dictionary<object, T> CreateCollectionDictionary<T>(ICollection<T> collection)
        {
            Dictionary<object, T> collectionMap = new Dictionary<object, T>();

            foreach (T item in collection)
            {
                try
                {
                    collectionMap[GetId(item)] = item;
                }
                catch (Ngilead.Core.Exception.NotPersistentObjectException)
                {
                    // not an entity : use hashcode instead
                    collectionMap[item.GetHashCode()] = item;
                }
                catch (Ngilead.Core.Exception.TransientObjectException)
                {
                    // transient entity : use hashcode instead
                    collectionMap[item.GetHashCode()] = item;
                }
            }

            return collectionMap;
        }

        /// <summary>
        /// (Re)create the original map
        /// </summary>
        /// <typeparam name="K"></typeparam>
        /// <typeparam name="V"></typeparam>
        /// <param name="proxyInformations"></param>
        /// <param name="map"></param>
        /// <returns></returns>
        public Dictionary<K, V> CreateOriginalDictionary<K, V>(Dictionary<string, object> proxyInformations,
                                                               Dictionary<K, V> map)
        {
            //  Precondition checking
            //
            if (proxyInformations.ContainsKey(ID_LIST) == false)
            {
                return null;
            }
            List<SerializableId> keyList = (List<SerializableId>)proxyInformations[ID_LIST];
            if (keyList == null)
            {
                return null;
            }

            //  Dictionary creation
            //
            List<SerializableId> valueList = (List<SerializableId>)proxyInformations[VALUE_LIST];

            //	Create maps(ID -> entity)
            //
            Dictionary<object, K> keyMap = CreateCollectionDictionary(map.Keys);
            Dictionary<object, V> valueMap = CreateCollectionDictionary(map.Values);

            //	Fill snapshot map
            //
            Dictionary<K, V> snapshot = new Dictionary<K, V>();

            foreach (SerializableId sid in keyList)
            {
                snapshot.Add(CreateOriginalEntity(sid, keyMap),
                             CreateOriginalEntity(valueList[keyList.IndexOf(sid)], valueMap));
            }

            return snapshot;
        }


        /// <summary>
        /// Test if the two argument collection are the same or not
        /// </summary>
        /// <param name="coll1"></param>
        /// <param name="coll2"></param>
        /// <returns></returns>
        private bool AreDifferent(ICollection coll1, ICollection coll2)
        {
            //	Precondition checking
            if (coll1 == null)
            {
                return (coll2 != null && coll2.Count != 0);
            }

            if (coll2 == null)
            {
                return coll1.Count != 0;
            }

            //	Size comparison
            //
            if (coll1.Count != coll2.Count)
            {
                return true;
            }

            //	Item comparison
            //
            if ((coll1 is IList))
            {
                //	Compare content *and* order
                object[] array1 = new object[coll1.Count];
                coll1.CopyTo(array1, 0);

                object[] array2 = new object[coll2.Count];
                coll2.CopyTo(array2, 0);


                for (int index = 0; index < array1.Length; index++)
                {
                    if (array1[index] != array2[index])
                    {
                        return true;
                    }
                }
            }
            else
            {
                bool isContained = false;
                // No order : just compare contents
                foreach (var itemC1 in coll1)
                {
                    foreach (var itemC2 in coll2)
                    {
                        if (itemC1 == itemC2)
                        {
                            isContained = true;
                            break;
                        }
                    }
                    if (!isContained)
                    {
                        return true;
                    }
                }
                return false;
            }
            //	Same collections
            return false;
        }

        /// <summary>
        /// (Re)create the original collection
        /// </summary>
        /// <param name="proxyInformations"></param>
        /// <param name="collection"></param>
        /// <returns></returns>
        public ICollection CreateOriginalCollection<T>(Dictionary<string, object> proxyInformations, ICollection collection)
        {
            if (proxyInformations.Keys.Contains(ID_LIST))
            {
                List<T> original = new List<T>();
                List<SerializableId> idList = (List<SerializableId>)proxyInformations[ID_LIST];
                if (idList == null)
                {
                    idList = new List<SerializableId>();
                    logger.Warn("proxyInformations was null, a new list is instanciated.");
                }
                //	Create map(ID -> entity)
                Dictionary<object, T> collectionMap = CreateCollectionDictionary<T>(collection);

                //	Fill snapshot
                foreach (var sid in idList)
                {
                    original.Add(CreateOriginalEntity<T>(sid, collectionMap));
                }
                return original;
            }
            else
            {
                return null;
            }
        }

        /// <summary>
        /// Create an entity from its serializable id.
        /// The entity is taken from the argument map in priority.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="sid"></param>
        /// <param name="collectionMap"></param>
        /// <returns></returns>
        private T CreateOriginalEntity<T>(SerializableId sid,
                                       Dictionary<object, T> collectionMap)
        {
            //	Precondition checking
            //
            T entity = default(T);
            if (sid.Id != null)
            {
                // Is the entity still present ?

                if (!collectionMap.Keys.Contains(sid.Id))
                {
                    //	deleted item
                    try
                    {
                        entity = (T)GetSession().Session.Load(sid.EntityName, sid.Id);
                    }
                    catch (ObjectNotFoundException ex)
                    {
                        //	The data has already been deleted, just remove it from the collection
                        logger.Warn(String.Format("Deleted entity : {0} cannot be retrieved from DB and thus added to snapshot", sid), ex);
                    }
                }
                else
                {
                    entity = collectionMap[sid.Id];
                }
            }
            else // if (sid.getHashCode() != null)
            {
                if (!collectionMap.Keys.Contains(sid.GetHashCode()))
                {
                    //	deleted item
                    //
                    entity = Activator.CreateInstance<T>();
                }
                else
                {
                    entity = collectionMap[sid.GetHashCode()];
                }
            }

            return entity;
        }

        private Dictionary<object, T> CreateCollectionDictionary<T>(ICollection collection)
        {
            Dictionary<object, T> collectionMap = new Dictionary<object, T>();
            if (collection != null)
            {
                foreach (T item in collection)
                    try
                    {
                        collectionMap.Add(GetId(item), item);
                    }
                    catch (Ngilead.Core.Exception.NotPersistentObjectException)
                    {
                        // not persistent entity : use hashcode instead
                        collectionMap.Add(item.GetHashCode(), item);
                    }
                    catch (Ngilead.Core.Exception.TransientObjectException)
                    {
                        // transient entity : use hashcode instead
                        collectionMap.Add(item.GetHashCode(), item);
                    }
            }

            return collectionMap;
        }

        /// <summary>
        /// Create a list of serializable ID for the argument collection
        /// </summary>
        /// <param name="collection"></param>
        /// <returns></returns>
        private List<SerializableId> CreateIdList(ICollection collection)
        {
            //	Precondition checking
            //
            if ((collection == null) || (collection.Count == 0))
            {
                return null;
            }

            List<SerializableId> idList = new List<SerializableId>();

            foreach (var item in collection)
            {
                if (item != null)
                {
                    SerializableId id = new SerializableId();
                    if (IsPersistentPojo(item))
                    {
                        id.EntityName = GetEntityName(GetPersistentClass(item), item);
                        id.Id = GetId(item);
                    }
                    else
                    {
                        id.EntityName = item.GetType().FullName;
                        id.HashCode = item.GetHashCode();
                    }

                    idList.Add(id);
                }
            }

            if (idList.Count == 0)
            {
                return null;
            }
            else
            {
                return idList;
            }
        }

        /// <summary>
        /// Compute embedded persistence (Component, UserType) for argument class
        /// </summary>
        /// <param name="clazz"></param>
        private void ComputePersistenceForClass(Type clazz)
        {
            //	Precondition checking
            //
            lock (_persistenceMap)
            {
                if (clazz != null && _persistenceMap.Keys.Contains(clazz))
                {
                    //	already computed
                    return;
                }
            }

            //	Get associated metadata
            //
            List<String> entityNames = GetEntityNamesFor(clazz);
            if ((entityNames == null) ||
                (entityNames.Count == 0))
            {
                //	Not persistent : check implemented interfaces (they can be declared as persistent !!)
                Type[] interfaces = clazz.GetInterfaces();
                if (interfaces != null)
                {
                    for (int index = 0; index < interfaces.Length; index++)
                    {
                        if (IsPersistentClass(interfaces[index]))
                        {
                            MarkClassAsPersistent(clazz, true);
                            return;
                        }

                    }
                }

                //	Not persistent and no persistent interface!
                //
                MarkClassAsPersistent(clazz, false);
                return;
            }

            //	Persistent class
            //
            MarkClassAsPersistent(clazz, true);

            //	Look for component classes
            //
            foreach (var entityName in entityNames)
            {
                IType[] types = SessionFactory.GetClassMetadata(entityName).PropertyTypes;
                for (int index = 0; index < types.Length; index++)
                {
                    IType type = types[index];
                    if (logger.IsDebugEnabled)
                    {
                        logger.Debug(String.Format("Scanning type {0} from {1}", type.Name, clazz));
                    }
                    ComputePersistentForType(type);
                }
            }
        }

        /// <summary>
        /// Compute persistent for Hibernate type
        /// </summary>
        /// <param name="type"></param>
        private void ComputePersistentForType(IType type)
        {
            //	Precondition checking
            //
            lock (_persistenceMap)
            {
                if (type.ReturnedClass != null && _persistenceMap.Keys.Contains(type.ReturnedClass))
                {
                    //	already computed
                    return;
                }
            }

            if (logger.IsDebugEnabled)
            {
                logger.Debug(String.Format("Scanning type {0}", type.Name));
            }

            if (type.IsComponentType)
            {
                //	Add the Class to the persistent map
                if (logger.IsDebugEnabled)
                {
                    logger.Debug(String.Format("Type {0} is component type", type.Name));
                }

                MarkClassAsPersistent(type.ReturnedClass, true);

                IType[] subtypes = ((IAbstractComponentType)type).Subtypes;
                for (int index = 0; index < subtypes.Length; index++)
                {
                    ComputePersistentForType(subtypes[index]);
                }
            }
            else if (typeof(IUserType).IsAssignableFrom(type.ReturnedClass))
            {
                //	Add the Class to the persistent map
                if (logger.IsDebugEnabled)
                {
                    logger.Debug(String.Format("Type {0} is user type", type.Name));
                }

                MarkClassAsPersistent(type.ReturnedClass, true);
            }
            else if (type.IsCollectionType)
            {
                //	Collection handling
                if (logger.IsDebugEnabled)
                {
                    logger.Debug(String.Format("Type {0} is collection type", type.Name));
                }
                ComputePersistentForType(((CollectionType)type).GetElementType(((SessionFactoryImpl)SessionFactory)));
            }
            else if (type.IsEntityType)
            {
                if (logger.IsDebugEnabled)
                {
                    logger.Debug(String.Format("Type {0} is entity type", type.Name));
                }
                ComputePersistenceForClass(type.ReturnedClass);
            }
        }

        /// <summary>
        /// Mark class as persistent or not
        /// </summary>
        /// <param name="clazz"></param>
        /// <param name="persistent"></param>
        private void MarkClassAsPersistent(Type clazz, bool persistent)
        {
            if (logger.IsDebugEnabled)
            {
                if (persistent)
                {
                    logger.Debug(String.Format("Marking {0} as persistent", clazz));
                }
                else
                {
                    logger.Debug(String.Format("Marking {0} as not persistent", clazz));
                }
            }
            lock (_persistenceMap)
            {
                //	Debug check
                //
                if (clazz != null && _persistenceMap.Keys.Contains(clazz) == false)
                {
                    _persistenceMap.Add(clazz, persistent);
                }
                else
                {
                    //	Check persistence information
                    //
                    if (persistent != _persistenceMap[clazz])
                    {
                        throw new System.Exception("Invalid persistence state for " + clazz);
                    }
                }
            }
        }

        /// <summary>
        /// Return the already opened session (returns null if none is opened)
        /// </summary>
        /// <returns></returns>
        protected ISession GetCurrentSession()
        {
            //	Precondition checking
            //
            if (SessionFactory == null)
            {
                throw new ArgumentNullException("No Hibernate Session Factory defined !");
            }

            //	Open the existing session
            //
            ISession session = null;
            try
            {
                session = SessionFactory.GetCurrentSession();
                if (session.IsConnected == false)
                {
                    return null;
                }
            }
            catch (HibernateException ex)
            {
                if (logger.IsDebugEnabled)
                {
                    logger.Debug("Exception during getCurrentSession", ex);
                }
                return null;
            }

            return session;
        }

        /// <summary>
        /// Check if the id equals the unsaved value or not
        /// </summary>
        /// <param name="pojo"></param>
        /// <param name="id"></param>
        /// <param name="?"></param>
        /// <returns></returns>
        private bool IsUnsavedValue(object pojo, object id, Type persistentClass)
        {
            //	Precondition checking
            if (id == null)
            {
                return true;
            }

            //	Get unsaved value from entity metamodel
            IEntityPersister entityPersister = ((SessionFactoryImpl)SessionFactory).GetEntityPersister(GetEntityName(persistentClass, pojo));
            EntityMetamodel metamodel = entityPersister.EntityMetamodel;
            IdentifierProperty idProperty = metamodel.IdentifierProperty;
            bool? result = idProperty.UnsavedValue.IsUnsaved(id);

            if (!result.HasValue)
            {
                // Unsaved value undefined
                return false;
            }
            else
            {
                return result.Value;
            }
        }

        /// <summary>
        /// Return the underlying persistent class
        /// </summary>
        /// <param name="pojo"></param>
        /// <returns></returns>
        private Type GetPersistentClass(object pojo)
        {
            if (pojo is INHibernateProxy)
            {
                return ((INHibernateProxy)pojo).HibernateLazyInitializer.PersistentClass;
            }
            else
            {
                return pojo.GetType();
            }
        }

        /// <summary>
        /// Debug method : dump persistence map for checking
        /// </summary>
        private void DumpPersistenceMap()
        {
            lock (_persistenceMap)
            {
                // 	Dump every entry
                //
                logger.Debug("-- Start of persistence map --");

                foreach (var persistenceEntry in _persistenceMap)
                {
                    logger.Debug(String.Format("{0} persistence is {1}", persistenceEntry.Key, persistenceEntry.Value));
                }

                logger.Debug("-- End of persistence map --");
            }
        }

        /// <summary>
        /// Get Hibernate class metadata
        /// </summary>
        /// <param name="clazz"></param>
        /// <param name="pojo"></param>
        /// <returns></returns>
        private String GetEntityName(Type clazz, Object pojo)
        {
            //	Direct metadata search
            IClassMetadata metadata = SessionFactory.GetClassMetadata(clazz);
            if (metadata != null)
            {
                return metadata.EntityName;
            }

            //	Iterate over all metadata to prevent entity name bug
            //	(if entity-name is redefined in mapping file, it is not found with
            //	_sessionFatory.getClassMetada(clazz); !)
            List<String> entityNames = GetEntityNamesFor(clazz);

            //	check entity names
            //
            if (entityNames.Count == 0)
            {
                //	Not found
                return GetUnenhancedClass(clazz).Name;
            }
            else if (entityNames.Count == 1)
            {
                //	Only one entity name
                return entityNames[0];
            }

            //	More than one entity name : need pojo to know which one is the right one
            if (pojo != null)
            {
                // Get entity name
                return ((SessionImpl)GetSession().Session).BestGuessEntityName(pojo);
            }
            else
            {
                throw new ArgumentNullException("Missing pojo for entity name retrieving !");
            }
        }


        /// <summary>
        /// 
        /// </summary>
        /// <param name="clazz"></param>
        /// <returns>all possible entity names for the argument class.</returns>
        private List<String> GetEntityNamesFor(Type clazz)
        {
            List<string> entityNames = new List<string>();
            IDictionary<string, IClassMetadata> allMetadata = SessionFactory.GetAllClassMetadata();
            foreach (var classMetadata in allMetadata)
            {
                if (clazz == classMetadata.Value.GetMappedClass(EntityMode.Poco))
                {
                    entityNames.Add(classMetadata.Value.EntityName);
                }
            }
            return entityNames;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <returns>the current session (open a new one if needed)</returns>
        private NHibernateSession GetSession()
        {
            NHibernateSession hSession = _session.Get();
            if (hSession == null)
            {
                OpenSession();
                hSession = _session.Get();
            }
            return hSession;
        }

        /// <summary>
        /// Remove the proxy around the type
        /// </summary>
        /// <param name="type"></param>
        /// <returns></returns>
        private Type UnenhanceClass(Type type)
        {
            bool enhanced = true;

            while (type != null && enhanced)
            {

                //TODO BM : Good step ? NHibernate dependant problem maybe ?
                if (type.Assembly.GetName().Name.StartsWith("DynamicProxyGenAssembly"))
                {
                    type = type.BaseType;
                }
                else
                {
                    enhanced = false;
                }
            }
            return type;
        }

        private Type GetType(string typeName)
        {
            //Finding the generic type 
            Type itemType = Type.GetType(typeName);
            if (itemType != null)
            {
                return itemType;
            }

            //  Interface ? Search on all assemblies !
            //
            AppDomain MyDomain = AppDomain.CurrentDomain;
            Assembly[] AssembliesLoaded = MyDomain.GetAssemblies();
            foreach (Assembly currentAssembly in AssembliesLoaded)
            {
                itemType = currentAssembly.GetType(typeName);
                if (itemType != null)
                {
                    return itemType;
                }
            }

            //  Not found : force the exception
            //
            throw new CloneException("Unable to find type : " + typeName, null);
        }

        #endregion
        /*----------------------------------------------------------------------------------------*/
    }


    /// <summary>
    /// Structure for new items (needs ordering)
    /// </summary>
    public class NewItem
    {
        public Object Obj { get; set; }
        public int Index { get; set; }
    }

    public class NHibernateSession
    {
        public ISession Session { get; set; }
        public bool Created { get; set; }

        public NHibernateSession(ISession session, bool created)
        {
            this.Session = session;
            this.Created = created;
        }
    }
}
